/******************************************************************************/
package jamie.org.sil.www.usxUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;



/******************************************************************************/
/**
 * Miscellaneous processing concerned with handling input and output files.
 * There's a lot to this, so it's going to take a long time to describe,
 * unfortunately.  Originally I did consider splitting this into two separate
 * classes, one concerned with input and one concerned with output.  However,
 * the two needed to share input in such an intimate manner that this seemed
 * undesirable on further consideration.
 * <p>
 * 
 * Book assembly is concerned with quite a number of different things.  It
 * addresses the matter of identifying all the input files; of associating
 * input files with the names to be used for output files; of providing support
 * when working out how to formulate hyperlinks between files; etc.
 * 
 * A fair bit of this is influenced by configuration information relating
 * specifically to book assembly, so let's start off by examining that.
 *
 *
 *
 * <h3>About the book assembly information</h3>
 * The book assembly configuration data contains a list of the files which
 * are to serve as input to the conversion and assembly process.  This
 * covers absolutely everything -- scripture, peripheral files, notes,
 * TOC, etc.
 * <p>
 *
 * The <i>content</i> of the book assembly file determines what is
 * processed (at least to a first approximation -- see below); and the
 * <i>order</i> in which items appear in the book assembly file
 * determines the order in which things are listed in any TOC, and also
 * (where output is amalgamated into combined files) the order in
 * which items appear in these combined files.
 * <p>
 *
 * I mentioned that the content determines what gets processed "to a
 * first approximation".  It is valid to name in the book assembly
 * data scripture books for which no files actually exist, and such
 * entries are ignored.  This is useful, because it means you can set
 * up a generic book assembly file to be used regardless of the inputs
 * you actually have available.  (The only time when you'd actually
 * need to set up specific book assembly files would be if the input
 * folder contained files which you specifically did <i>not</i> want
 * to process -- for example if you have files for an entire Bible, but
 * want to produce output only for the NT).
 * <p>
 *
 * The book assembly information may also list peripheral files.
 * The present class provides some support for these, in that
 * if requested to do so, it will run over the list, find those
 * files which actually exist, and copy them (and any associated
 * image files) to the output folder.  In determining whether this
 * processing is appropriate or not, it looks at settings in the
 * FeatureManager which will have been established by the converter.
 * The manner in which the elements in the book assembly information
 * are related to specific peripheral files and / or image files is
 * discussed later.
 * <p>
 * 
 * There is one additional twist as regards peripheral files, in that some
 * files we would expect to be defined in advance, but some (such as notes
 * and TOC) are generated by the present processing (if they are generated
 * at all).  If you potentially require these files in the final output, they
 * must be identified at the position you require in the book assembly data,
 * but there is no requirement that they actually be generated on a particular
 * run -- if you decide, for example, that on a particular run you don't want
 * to generate a TOC, then it simply won't be included in the assembly.
 *
 *
 *
 *
 *
 * <h3>bookElement tags within the book assembly configuration data</h3>
 * Book assembly configuration information is made up of a number of bookElement
 * tags which serve several related but slightly different roles, as implied
 * in the foregoing discussion.
 * <p>
 * 
 * A couple of examples are given below, one for scripture and one for
 * peripheral material :-
 * <p>
 * <code>
 *  &lt;bookElement type="scripture" group="OT/Pentateuch" ubsAbbreviation="GEN"&gt;&lt;/bookElement>&gt;
 *  &lt;bookElement type="peripheral" group="backMatter" vernacularStringSelector="chronology"&gt;myChronology.htm&lt;/bookElement&gt;
 * </code>
 * 
 * As noted above, the order in which items appear within the list of bookElements
 * determines the order in which they will appear in any table of contents, except
 * that scripture books come out in Bible order regardless of the order in which
 * they are mentioned.  (Deutero-canonical books also come out in a predefined
 * order, as established by UBS.)
 * <p>
 *
 * Each bookAssembly tag identifies a single chunk of input or output.  You will
 * observe that in the case of peripheral material, the tag gives a value which
 * is the name of a file.  In the case of scriptural material, no file is given,
 * because the system deduces the name of the file from the UBS abbreviation.
 * In both cases, other configuration parameters tell the system which folder to
 * look in for the data.
 * <p>
 * 
 * Each bookElement carries a "type" attribute, which should be drawn
 * from the list toc; scripture; notes or peripheral.  toc, scripture and notes
 * outputs are generated as part of the conversion process.  "peripheral" entries
 * must identify files which already exist and are in the format required by
 * the target.  These are merely copied to the output folder from the folder
 * identified by inputPeripheralMaterialFolderPath.
 * <p>
 * 
 * Each bookElement also carries a "group" attribute, which enables similar
 * information to be grouped together.  There is a standard list of groups --
 * frontMatter; OT/???; NT/Gospels; NT/Acts; NT/Epistles; NT/Revelation; DC/???;
 * and backMatter.  (The question marks merely reflect the fact that I can't
 * recall what subgroups are available for OT and deutero-canon.)  What uses
 * this information I can't remember -- possibly OTML.
 * <p>
 *
 * Each element optionally also carries an "inToc" attribute (taking the
 * values yes or no), which determine whether that item should feature in
 * any table of contents.  If omitted this defaults to yes.  You would normally
 * set it to no only on the TOC itself, and on any notes element, since notes
 * are normally accessed directly from the text, rather than by navigating
 * to them from the TOC.
 * <p>
 *
 * Everything other than scripture references must also carry a
 * vernacularStringSelector attribute.  The value of this is used to link to
 * a similarly-named element in the vernacular parms configuration information,
 * which gives a string to appear in the TOC to point to this particular item.
 * <p>
 * 
 * Note that peripheral material and book assembly information in general has
 * not been a priority in the standards, and it therefore remains somewhat ill-
 * defined.  It is therefore quite possibly that the foregoing discussion will
 * need to be revised in the not too distant future.
 * <p>
 *
 * Processing in the present class can be used to copy peripheral items
 * to the output folder on behalf of the converter.
 * <p>
 *
 *
 *
 *
 *
 * <h3>Other build-related configuration parameters</h3>
 * There is an additional set of build-related parameters which it is
 * convenient to discuss here.  These are the "disposition"
 * parameters, which determine things like whether scripture output
 * ends up in one file per book, or in one overall file.  These are as
 * set out below.  There is a slight complication here, in that currently
 * several different parts of the system try to get these into an
 * appropriate form.  Most converters invoke the FeatureManager to
 * ensure that all necessary disposition parameters have been specified,
 * and to ensure that they are in line with what the converter can
 * actually handle.  In some cases, the converters also do their own
 * massaging of the parameters where the generic facilities of the
 * FeatureManager are not adequate.  And then the present class does
 * its own thing in the <i>canonicaliseConfig</i> method.  A fair
 * portion of this is concerned with validation, but it may also
 * override some parameters in order to make them compatible with
 * others.  I <i>hope</i> all these overrides are sensible and
 * necessary (ie that they are compatible with what the converter
 * itself would want, and do not contradict what it is already doing
 * either itself or via the FeatureManager).  However, I have to admit
 * that the whole area has become rather complicated, and at present
 * I don't have the time to go back and unravel and check it all.
 *
 * <ul>
 *  <li>outputOverallDisposition / outputOverallFileName: The former of these
 *      determines whether all outputs (scripture and all peripherals) are
 *      collected into a single file, or remain as individual files.  The
 *      latter gives the name for the file where we are collecting inputs
 *      into one file.</li>
 *
 *  <li>outputScriptureTextDisposition / outputScriptureTextFileName: The former
 *      of these determines whether scripture output goes to single files or is
 *      combined into a single file.  If combined, outputScriptureTextFileName
 *      must give the name of the file to which the combined output goes
 *      (filename only -- the path is specified elsewhere).</li>
 *
 *  <li>outputNotesDisposition / outputNotesFileName: The former of these
 *      determines whether (assuming there are any footnotes and cross-references
 *      in the input), these items are written to the text files, to separate
 *      files, one for each scripture file, or to a single output file.  In the
 *      latter case, the second tag gives the name of the file.</li>
 *
 *  <li>outputTocDisposition/ outputTocFileName: The former determines whether
 *      we have a TOC at all, and if so whether, in respect of the scripture
 *      files, we have one entry for each book, or one for each book and for each
 *      chapter.  Unless "none" is selected, the second tag must be used to
 *      give the filename to which the output will be written.</li>
 *
 *  <li>outputIntroductionDisposition: Determines whether any introductory material
 *      is written to the text file, to the notes file, or is dropped.</li>
 * </ul>
 *
 *
 *
 *
 *
 * <h3>Footnotes etc</h3>
 * Notes processing is particularly complicated, because there are
 * a large number of disposition options which can affect the
 * notes output, and these interact with the various ways in
 * which the text output is handled.  Note, incidentally, that
 * currently I'm assuming that notes and cross-references are
 * both handled together.
 * <p>
 *
 * Probably the only way to handle this is to run through all
 * the options for <i>outputNotesDisposition</i>.  The discussion
 * below indicates the kind of processing we would expect an
 * "average" converter to carry out.  As a reminder of what we have
 * already said, whether a converter actually follows this recommendation
 * is down to the converter itself -- we don't rely on any of this here.
 *
 * <ul>
 *    <li>None: Easy -- don't generate any notes (even if the input text
 *        contains notes information).</li>
 *
 *    <li>InSituInTextFile: Notes are simply output directly to the text
 *        file at the position where they are encountered in the input.
 *        (This is useful, for instance, with the OSIS converter, because
 *        OSIS itself sorts out where the notes output should ultimately
 *        appear.)  In such a case, there would not normally be any link
 *        from the text to the notes or vice versa.</li>
 *
 *    <li>AtEndOfTextFile: The converter should defer any notes to the end of
 *        the text file currently being generated.  It may well choose to
 *        insert hyperlinks between the text and the notes (in both
 *        directions).  If it does, it may wish to take into account that
 *        the two will appear in the same file, and hyperlinks can reflect
 *        this fact.  Hyperlinking is discussed in more detail shortly.</li>
 *
 *    <li>PerTextFile: The converter should create a separate file, one
 *        for each scripture file, and place the notes in that.  Note that
 *        if all scripture output is being routed to a single file, the
 *        implication is that all notes will also go to a single file.
 *        Hyperlinks will need to be adjusted accordingly.</li>
 *
 *    <li>Combined: Regardless of the disposition of the text outputs,
 *        the converter should produce a single notes output, and hyperlinks
 *        will need to take into account the fact that the notes will be
 *        in this file.</i>
 * </ul>
 * 
 * Cross-references and introductory material can be handled independently of
 * footnotes, but use basically the same options (except that they also support
 * the "WithFootnotes/WithEndNotes" options, which arrange for them to go
 * wherever the footnotes or endnotes aregoing.
 *
 *
 *
 *
 *
 * <h3>Hyperlinks</h3>
 * Where notes are being generated, and the notes do not appear in situ
 * in the text file, it is common practice to have a hyperlink from the
 * text file to the corresponding note, and from the note back to the
 * text.  Equally, where notes contain scripture references, it is common
 * to make these point to the appropriate text.
 * <p>
 *
 * When generating these hyperlinks, converters need to take into
 * account the relative placement of the "from" and "to" ends of the
 * link, since links which point between files need to name the
 * file, whereas links within a single file need not do so.  Depending
 * upon how the converter works, there may be a further complication,
 * in that as we will discuss later, we may be called upon to merge
 * multiple outputs into a single file.  Knowing this, the converter
 * <i>may</i> opt to write all the outputs straight to this file as it
 * creates them.  However, there is no reason why it should not write
 * outputs to individual files, and then carry out the merge process
 * at the end.  If it works in this latter way, it will probably want
 * to take into account, during the initial file generation phase,
 * the fact that the final disposition of the data (and the filenames
 * involved) may differ from the arrangement which it is currently
 * temporarily generating.
 * <p>
 *
 * To cater for the possibility of outputs eventually being merged
 * into combined files, converters may want to ensure that any
 * anchors they generate are globally unique, and not just unique
 * within the context of the processing for a single input file.
 * <p>
 *
 * The present class does what it can to provide the information
 * converters might need in order to process hyperlinks correctly.
 * However, it will make sense to defer discussion of this until
 * we have looked at the issue of file merging.
 *
 *
 *
 *
 *
 * <h3>File output</h3>
 * Although we are unlikely to use the full flexibility of the present class,
 * it is possible to route scripture text, cross-references, introductory
 * material, and footnotes separately.  The present class will handle all of
 * this automatically, but to do that, you must call startRun before you start
 * generating output, startBook at the start of each book, startChapter at the
 * start of each chapter, and endRun at the end of the run (ie after you have
 * finished generating output).  Before calling startBook and startChapter,
 * you must make sure that you have set up the Bookmark instance appropriately.
 *
 *
 *
 *
 *
 * <h3>Miscellaneous</h3>
 * Mention was made of peripheral files earlier.  The processing available
 * in the present class is based upon that needed by the XHTML converter,
 * which makes the assumption that all peripheral files are already in
 * the form (XHTML in this case) required by the converter, and that
 * therefore no processing is required, beyond ensuring that the files
 * are copied to the correct location.  This processing may be applicable
 * to other converters in future, but then again, it may not.
 * <p>
 *
 * There is an issue with the ePub converter, which
 * presently can optionally pick up and use a cover graphic,
 * independently of whether the conversion process as a whole is
 * using peripheral material.  Because this is specific to a single
 * converter at present, this is handled within the converter
*/

public class BookAssembly
{
  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                                 Public                                 **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /** Singleton.

      @return instance
   */

  public static BookAssembly instance ()
  {
    if (null == m_Instance) m_Instance = new BookAssembly();
    return m_Instance;
  }


  /****************************************************************************/
  /* Copy selected CSS files to a subfolder of the output folder.  Also
     arrange to copy any necessary peripheral and image files. */
  
  public void copyNonScriptureFiles () throws IOException
  {
    copyFiles(Config.instance().getOutputFolderPath() + "css/", Config.instance().getCssFilePaths(), "");
    if (FeatureManager.instance().getAcceptPeripheralsDirectivesFromBookAssembly())
      copyImageAndPeripheralFiles();
  }



  /****************************************************************************/
  /**
   * Returns the file name (not path) of the file which will eventually be
   * used for cross references, taking into account all configuration options.
   * Bear in mind that this will be used mainly to build up references from
   * scripture to cross references, and so can be blank where the two will be 
   * co-located.
   * 
   * @return File name (blank if no cross references are to be generated, or if
   *         cross references are to be co-located with scripture.
   * 
   * @throws ConverterException if disposition is not recognised
   */
  
   public String getUltimateFileNameForCrossReferences ()
   {
     String disposition = Config.instance().getOutputCrossReferencesDisposition();
     
     if ("WithFootnotes".equalsIgnoreCase(disposition)) return getUltimateFileNameForFootnotes();

     if ("WithEndnotes".equalsIgnoreCase(disposition)) return getUltimateFileNameForEndnotes();

     if ("Combined".equalsIgnoreCase(Config.instance().getOutputOverallDisposition())) return "";

     if ("None".equalsIgnoreCase(disposition)) return "";

     if ("AtEndOfTextFile".equalsIgnoreCase(disposition)) return "";

     if ("InSituInTextFile".equalsIgnoreCase(disposition)) return "";

     if ("Combined".equalsIgnoreCase(disposition)) return Config.instance().getOutputCrossReferencesFileName();
    
     if ("PerTextFile".equalsIgnoreCase(disposition)) return getUltimateFileNameForScripture();

     throw new ConverterException(ConverterException.Action.AbandonRunWhenever,
                                  ConverterException.Log.Error,
                                  "CrossReferences option '" + disposition + "' not fully implemented.");
  }


  /****************************************************************************/
  /**
   * Returns the file name (not path) of the file which will eventually be
   * used for endnotes, taking into account all configuration options.
   * Bear in mind that this will be used mainly to build up references from
   * scripture to endnotes, and so can be blank where the two will be 
   * co-located.
   * 
   * @return File name (blank if no endnotes are to be generated, or if
   *         endnotes are to be co-located with scripture.
   * 
   * @throws ConverterException if disposition is not recognised
   */
  
   public String getUltimateFileNameForEndnotes ()
   {
     String disposition = Config.instance().getOutputFootnotesDisposition();
     
     if ("WithFootnotes".equalsIgnoreCase(disposition)) return getUltimateFileNameForFootnotes();

     if ("Combined".equalsIgnoreCase(Config.instance().getOutputOverallDisposition())) return "";

     if ("None".equalsIgnoreCase(disposition)) return "";

     if ("AtEndOfTextFile".equalsIgnoreCase(disposition)) return "";

     if ("InSituInTextFile".equalsIgnoreCase(disposition)) return "";

     if ("Combined".equalsIgnoreCase(disposition)) return Config.instance().getOutputEndnotesFileName();
    
     if ("PerTextFile".equalsIgnoreCase(disposition)) return getUltimateFileNameForScripture();

     throw new ConverterException(ConverterException.Action.AbandonRunWhenever,
                                  ConverterException.Log.Error,
                                  "Endnotes option '" + disposition + "' not fully implemented.");
  }


  /****************************************************************************/
  /**
   * Returns the file name (not path) of the file which will eventually be
   * used for footnotes, taking into account all configuration options.
   * Bear in mind that this will be used mainly to build up references from
   * scripture to footnotes, and so can be blank where the two will be 
   * co-located.
   * 
   * @return File name (blank if no footnotes are to be generated, or if
   *         footnotes are to be co-located with scripture.
   * 
   * @throws ConverterException if disposition is not recognised
   */
  
   public String getUltimateFileNameForFootnotes ()
   {
     String disposition = Config.instance().getOutputFootnotesDisposition();

     if ("Combined".equalsIgnoreCase(Config.instance().getOutputOverallDisposition())) return "";

     if ("None".equalsIgnoreCase(disposition)) return "";

     if ("AtEndOfTextFile".equalsIgnoreCase(disposition)) return "";

     if ("InSituInTextFile".equalsIgnoreCase(disposition)) return "";

     if ("Combined".equalsIgnoreCase(disposition)) return Config.instance().getOutputFootnotesFileName();
    
     if ("PerTextFile".equalsIgnoreCase(disposition)) return getUltimateFileNameForScripture();

     throw new ConverterException(ConverterException.Action.AbandonRunWhenever,
                                  ConverterException.Log.Error,
                                  "Footnotes option '" + disposition + "' not fully implemented.");
  }


  /****************************************************************************/
  /**
   * Returns the file name (not path) of the file which will eventually be
   * used for introductory material, taking into account all configuration
   * options.  Bear in mind that this will be used mainly to build up references
   * from scripture to introductory material, and so can be blank where the two
   * will be  co-located.
   * 
   * @return File name (blank if no introductory material is to be generated, or
   *         if introductory material is to be co-located with scripture.
   * 
   * @throws ConverterException if disposition is not recognised
   */
  
   public String getUltimateFileNameForIntroduction ()
   {
     String disposition = Config.instance().getOutputCrossReferencesDisposition();
     
     if ("WithFootnotes".equalsIgnoreCase(disposition)) return getUltimateFileNameForFootnotes();

     if ("WithEndnotes".equalsIgnoreCase(disposition)) return getUltimateFileNameForEndnotes();

     if ("Combined".equalsIgnoreCase(Config.instance().getOutputOverallDisposition())) return "";

     if ("None".equalsIgnoreCase(disposition)) return "";

     if ("AtEndOfTextFile".equalsIgnoreCase(disposition)) return "";

     if ("InSituInTextFile".equalsIgnoreCase(disposition)) return "";

     if ("Combined".equalsIgnoreCase(disposition)) return Config.instance().getOutputIntroductionFileName();
    
     if ("PerTextFile".equalsIgnoreCase(disposition)) return getUltimateFileNameForScripture();

     throw new ConverterException(ConverterException.Action.AbandonRunWhenever,
                                  ConverterException.Log.Error,
                                  "Introduction option '" + disposition + "' not fully implemented.");
  }


  /****************************************************************************/
  /**
   * Returns the file name (not path) of the file which will eventually be
   * used for scripture, taking into account all configuration options.
   *
   * This version is for use where you want details for the current book /
   * chapter.
   * 
   * @return File name (blank if no footnotes are to be generated, or if
   *         footnotes are to be co-located with scripture.
   */
  
   public String getUltimateFileNameForScripture ()
   {
     if ("Combined".equalsIgnoreCase(Config.instance().getOutputOverallDisposition()))
       return Config.instance().getOutputOverallFileName();

     String disposition = Config.instance().getOutputScriptureTextDisposition();

     if ("Combined".equalsIgnoreCase(disposition)) return Config.instance().getOutputScriptureTextFileName();
    
     int chapterNo = Bookmark.instance().getChapter();
     String ubsAbbreviation = Bookmark.instance().getUbsAbbreviatedName();
     String res = BookAssembly.instance().getBookElement(ubsAbbreviation).getOutputFileName(chapterNo);

     if ("PerChapter".equalsIgnoreCase(disposition))
     {
       String n = String.format("_%03d", chapterNo);
       int i = res.lastIndexOf(".");
       res = res.substring(0, i) + n + res.substring(i);
     }

     return res;
   }


  /****************************************************************************/
  /**
   * Returns the file name (not path) of the file which will eventually be
   * used for scripture, taking into account all configuration options.
   *
   * This version is for use where you have a specific book / chapter in
   * mind.
   *
   * @param ubsBookNo
   * @param chapterNo
   * 
   * @return File name (blank if no footnotes are to be generated, or if
   *         footnotes are to be co-located with scripture.
   */
  
   public String getUltimateFileNameForScripture (int ubsBookNo, int chapterNo)
   {
     if ("Combined".equalsIgnoreCase(Config.instance().getOutputOverallDisposition()))
       return Config.instance().getOutputOverallFileName();

     String disposition = Config.instance().getOutputScriptureTextDisposition();

     if ("Combined".equalsIgnoreCase(disposition)) return Config.instance().getOutputScriptureTextFileName();
    
     String ubsAbbreviation = BibleDetails.instance().getAbbreviatedUbsName(ubsBookNo);
     String res = BookAssembly.instance().getBookElement(ubsAbbreviation).getOutputFileName(chapterNo);

     if ("PerChapter".equalsIgnoreCase(disposition))
     {
       String n = String.format("_%03d", chapterNo);
       int i = res.lastIndexOf(".");
       res = res.substring(0, i) + n + res.substring(i);
     }

     return res;
   }


  /****************************************************************************/
  /**
     Carries out end-of-run processing.

     @throws IOException
  */

  public void endRun () throws IOException
  {
    endThing("PerChapter");
    endThing("PerBook");
    endThing("Combined");
  }
  
          
  /****************************************************************************/
  /**
     Get a given book element.
   
     @param ubsAbbreviation

     @return Book element.
  */

  public BookElement getBookElement (String ubsAbbreviation)
  {
    return m_BookElementsMap.get(ubsAbbreviation);
  }

  
  /****************************************************************************/
  /**
     Purely for use by iterator classes.

     @return Book elements list.
  */

  public ArrayList<BookElement> getBookElements ()
  {
    return m_BookElements;
  }

  
  /****************************************************************************/
  /**
     Get the cover element (if any).
    
     @return Cover element, or null if not found.
   */
  
  public BookElement getCoverElement ()
  {
    for (int i = 0; i < m_BookElements.size(); ++i)
    {
       BookElement be = m_BookElements.get(i);
       String s = be.getVernacularString();
       if (null != s && "cover".equalsIgnoreCase(s)) return be;
    }

    return null;
  }
  
  
  /****************************************************************************/
  /**
     Gets the book elements list.  Intended for use only with the various
     iterator classes.

     @return Book elements list.
  */

  public ArrayList<BookElement> getFileDetailsFromInputFolder ()
  {
    readFileListFromInputFolder();
    return m_BookElements;
  }

  
  /****************************************************************************/
  /**
   * Returns the number of the first chapter available in the current book.
   * 
   * @return Chapter number.
   */
  
  public int getFirstAvailableChapterNumberForCurrentBook ()
  {
    return m_FirstAvailableChapterNumberForBook;
  }
  
  
  /****************************************************************************/
  /**
     Returns a list of filenames and vernacular strings for all the things
     which are to feature in the TOC.
   
     @return List.
   */

  public ArrayList<String[]> getTocDetails () throws IOException
  {
    ArrayList<String[]> res = new ArrayList<String[]>();
    for (int i = 0; i < m_BookElements.size(); ++i)
    {
      BookElement be = m_BookElements.get(i);
      if (be.inToc())
      {
        String outputFileName = be.getOutputFileName(0);
        if (0 == outputFileName.length() && "peripheral".equalsIgnoreCase(be.getType())) outputFileName = be.getInputFileName();
        if (0 != outputFileName.length() && be.getInputFileExists())
        {
          String[] s = { be.getType(), be.getVernacularString(), outputFileName, be.getAnchorName() };
	  res.add(s);
        }
      }
    }

    return res;
  }


  /****************************************************************************/
  /**
   * Creates a new BookElement (used only by Config when reading config data).
   *
   * @param type            "scripture" / "peripheral" etc.
   * @param group           eg "NT/Epistles"
   * @param inToc           Yes or No.
   * @param ubsAbbreviation What it says.  Used in scripture entries only.
   * @param vernacularStringSelector The English-language string used to identify
   *                                 look up the corresponding vernacular string.
   *                                 Used on peripheral entries only.
   * @param value           Any associated value.
   */
  
  public void newBookElement (String type,
                              String group,
                              String inToc,
                              String ubsAbbreviation,
                              String vernacularStringSelector,
                              String value)
  {
    BookElement be = (BookElement) new BookElementFromBookAssyFile(type, group, inToc, ubsAbbreviation, vernacularStringSelector, value);
    m_BookElements.add(be);
    m_BookElementsMap.put(ubsAbbreviation, be);
  }


  /****************************************************************************/
  /**
   * Determines if a given book / chapter combination exists in this output,
   * and is therefore available as the target for a cross-reference.
   *
   * @param ubsAbbreviation UBS abbreviation of book.
   *
   * @parm chapterNo
   *
   * @return true if it is.
   */

  public boolean referenceTargetExists (String ubsAbbreviation, int chapterNo)
  {
    if (!m_ListOfAllChaptersAvailableForCrossReferences.containsKey(ubsAbbreviation)) return false;
    HashMap<Integer, Object> p = m_ListOfAllChaptersAvailableForCrossReferences.get(ubsAbbreviation)      ;
    return p.containsKey(chapterNo);
  }


  /****************************************************************************/
  /**
     Set details of the header and trailer handlers.
     
     @param scripture Header and trailer handler for scripture files.
     @param notes Header and trailer handler for notes files.
     @param introduction Header and trailer handler for introduction files.
     @param crossReferences Header and trailer handler for cross reference files.
   */
  
  public void setHeaderAndTrailerWriters (OutputStreamHeaderAndTrailerWriter scripture,
                                          OutputStreamHeaderAndTrailerWriter notes,
                                          OutputStreamHeaderAndTrailerWriter introduction,
                                          OutputStreamHeaderAndTrailerWriter crossReferences)
  {
    m_HeaderAndTrailerWriterScripture = scripture;
    m_HeaderAndTrailerWriterNotes = notes;
    m_HeaderAndTrailerWriterIntroduction = introduction;
    m_HeaderAndTrailerWriterCrossReferences = crossReferences;
  }
  
  
  /****************************************************************************/
  /**
     Sets prefixes and suffixes etc -- see description of arguments..
   
     @param prefixText Used only where notes or cross-references are being
                       written to the main text file.  An array of strings
		       to be written to the main text file before the notes
		       or cross-reference material.  May be null if no
		       such text is required.  For example, in XHTML output,
		       we have been using "<br />" repeated a few times.
		       Note that I assume the same prefix is good for both
		       notes and cross-references.

     @param suffixText Used only where notes or cross-references are being
                       written to the main text file.  An array of strings
		       to be written to the main text file after the notes
		       or cross-reference material.  May be null if no
		       such text is required.  Note that I assume the same
		       prefix is good for both notes and cross-references.

     @param startOfTransferrableTextMarker 
                       Used only where notes or cross-references are being
                       written to the main text file.  I make the assumption
		       that the accumulated notes or cross-reference data
		       may have contain header information which needs
		       to be stripped off, in which case if this string
		       is non-null and non-empty, I strip off all lines
		       until I find one starting with this string.  For
		       example, in XHTML output, I have been looking for
		       "<!-- Start of notes -->".  If null, or empty,
		       I take <i>all</i> lines from the footnotes or
		       cross-reference file.  Note that I assume the
		       same marker is good for both footnotes and cross-
		       references.

     @param endOfTransferrableTextMarker 
                       Used only where notes or cross-references are being
                       written to the main text file.  I make the assumption
		       that the accumulated notes or cross-reference data
		       may have contain trailer information which needs
		       to be stripped off, in which case if this string
		       is non-null and non-empty, I strip off all lines
		       after the first one starting with this string.  For
		       example, in XHTML output, I have been looking for
		       "<!-- End of notes -->".  If null, or empty,
		       I take <i>all</i> lines from the footnotes or
		       cross-reference file.  Note that I assume the
		       same marker is good for both footnotes and cross-
		       references.

     @throws IOException
  */

  public void setPrefixesAndSuffixes (String[] prefixText,
                                      String[] suffixText,
                                      String startOfTransferrableTextMarker,
                                      String endOfTransferrableTextMarker)
  {
    m_PrefixText = prefixText;
    m_SuffixText = suffixText;
    m_StartOfTransferrableTextMarker = startOfTransferrableTextMarker;
    m_EndOfTransferrableTextMarker = endOfTransferrableTextMarker;
  }
  
          
  /****************************************************************************/
  /**
     Called at the start of processing for a new book.  Closes down any
     existing files, and opens new ones as necessary.

     @throws IOxception
  */

  public void startBook () throws IOException
  {
    /*------------------------------------------------------------------------*/
    /* Do end-of-book processing for anything already open. */
    
    endBook();


    
    /*------------------------------------------------------------------------*/
    BookElement be = m_BookElementsMap.get(Bookmark.instance().getUbsAbbreviatedName());
    m_FirstAvailableChapterNumberForBook =
      BibleDetails.instance().getFirstAvailableChapterNumberForBook(be.getInputPathName());

    
    
    /*------------------------------------------------------------------------*/
    /* Handle the main text file -- we want to open a new one if we are
       doing per book output. */
    
    String mainTextDisposition = Config.instance().getOutputScriptureTextDisposition();
    if ("PerBook".equalsIgnoreCase(mainTextDisposition))
    {
      String filePath = be.getOutputPathName(0);
      OutputManager.instance().setDestination(OutputManager.C_Destination_Scripture,
                                              filePath,
                                              Config.instance().getEncoding(),
                                              m_HeaderAndTrailerWriterScripture);
    }


    
    /*------------------------------------------------------------------------*/
    /* Handle footnotes.  Options for are as follows :-
       
       * None: Nothing to do.
       
       * Combined: Already taken care of in startRun -- nothing to do.
       
       * InSituInTextFile: Needs to point to the text file.  If we're doing
         a single combined text file, that will already have been set up
         anyway, but pointing to it again will do no harm.  If output is
         per book, then pointing to it is precisely what we want to do.
         If output is per chapter, then there's no harm in pointing -- it'll
         point at the wrong place, but we'll sort that out at the start of the
         chapter.
       
       * PerTextFile: Create a file if text output is per book.
       
       * AtEndOfTextFile: Create a temporary file if output is per book.
     */
    
    String disposition = Config.instance().getOutputFootnotesDisposition();
    if ("None".equalsIgnoreCase(disposition))
      ;
    else if ("Combined".equalsIgnoreCase(disposition))
      ;
    else if ("InSituInTextFile".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_Footnotes, OutputManager.C_Destination_Scripture);
    else if ("PerTextFile".equalsIgnoreCase(disposition) || "AtEndOfTextFile".equalsIgnoreCase(disposition))
    {
      if ("PerBook".equalsIgnoreCase(mainTextDisposition))
      {
        String filePath = be.getFootnotesPathName(0);
        OutputManager.instance().setDestination(OutputManager.C_Destination_Footnotes,
                                                filePath,
                                                Config.instance().getEncoding(),
                                                m_HeaderAndTrailerWriterNotes);
      }
    }

    
    
    /*------------------------------------------------------------------------*/
    /* Handle endnotes.  Options for are as follows :-
       
       * None: Nothing to do.
       
       * Combined: Already taken care of in startRun -- nothing to do.
       
       * InSituInTextFile: Needs to point to the text file.  If we're doing
         a single combined text file, that will already have been set up
         anyway, but pointing to it again will do no harm.  If output is
         per book, then pointing to it is precisely what we want to do.
         If output is per chapter, then there's no harm in pointing -- it'll
         point at the wrong place, but we'll sort that out at the start of the
         chapter.
     
       * WithFootnotes: Link to footnotes.
       
       * PerTextFile: Create a file if text output is per book.
       
       * AtEndOfTextFile: Create a temporary file if output is per book.
     */
    
    disposition = Config.instance().getOutputEndnotesDisposition();
    if ("None".equalsIgnoreCase(disposition))
      ;
    else if ("Combined".equalsIgnoreCase(disposition))
      ;
    else if ("InSituInTextFile".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_Endnotes, OutputManager.C_Destination_Scripture);
    else if ("WithFootNotes".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_Endnotes, OutputManager.C_Destination_Footnotes);
    else if ("PerTextFile".equalsIgnoreCase(disposition) || "AtEndOfTextFile".equalsIgnoreCase(disposition))
    {
      if ("PerBook".equalsIgnoreCase(mainTextDisposition))
      {
        String filePath = be.getEndnotesPathName(0);
        OutputManager.instance().setDestination(OutputManager.C_Destination_Endnotes,
                                                filePath,
                                                Config.instance().getEncoding(),
                                                m_HeaderAndTrailerWriterNotes);
      }
    }

    
    
    /*------------------------------------------------------------------------*/
    /* Handle cross-references.  These are basically the same as footnotes,
       except that we also need to cater for the "With*Notes" option.  We can
       handle this by pointing to the notes destination; the only time when it
       would be vaguely wrong to do this would be when the notes is itself
       wrong (pending us hitting the start of a chapter), but then we can
       sort the cross-references out too when we hit start of chapter. */
       
    disposition = Config.instance().getOutputCrossReferencesDisposition();
    if ("None".equalsIgnoreCase(disposition))
      ;
    else if ("Combined".equalsIgnoreCase(disposition))
      ;
    else if ("InSituInTextFile".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_CrossReferences, OutputManager.C_Destination_Scripture);
    else if ("WithFootNotes".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_CrossReferences, OutputManager.C_Destination_Footnotes);
    else if ("WithEndNotes".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_CrossReferences, OutputManager.C_Destination_Endnotes);
    else if ("PerTextFile".equalsIgnoreCase(disposition) || "AtEndOfTextFile".equalsIgnoreCase(disposition))
    {
      if ("PerBook".equalsIgnoreCase(mainTextDisposition))
      {
        String filePath = be.getCrossReferencesPathName(0);
        OutputManager.instance().setDestination(OutputManager.C_Destination_CrossReferences,
                                                filePath,
                                                Config.instance().getEncoding(),
                                                m_HeaderAndTrailerWriterCrossReferences);
      }
    }
 
    
    
    /*------------------------------------------------------------------------*/
    /* Handle introductory material -- as per cross-refernces. */
       
    disposition = Config.instance().getOutputIntroductionDisposition();
    if ("None".equalsIgnoreCase(disposition))
      ;
    else if ("Combined".equalsIgnoreCase(disposition))
      ;
    else if ("InSituInTextFile".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_Introduction, OutputManager.C_Destination_Scripture);
    else if ("WithFootnotes".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_Introduction, OutputManager.C_Destination_Footnotes);
    else if ("WithEndnotes".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_Introduction, OutputManager.C_Destination_Endnotes);
    else if ("PerTextFile".equalsIgnoreCase(disposition) || "AtEndOfTextFile".equalsIgnoreCase(disposition))
    {
      if ("PerBook".equalsIgnoreCase(mainTextDisposition))
      {
        String filePath = be.getIntroPathName(0);
        OutputManager.instance().setDestination(OutputManager.C_Destination_Introduction,
                                                filePath,
                                                Config.instance().getEncoding(),
                                                m_HeaderAndTrailerWriterIntroduction);
      }
    }
  }

  
  /****************************************************************************/
  /**
     Called at the start of processing for a new chapter.  Closes down any
     existing files, and opens new ones as necessary.

     @throws IOxception
  */

  public void startChapter () throws IOException
  {
    /*------------------------------------------------------------------------*/
    /* Do end-of-chapter processing for anything already open. */
    
    endChapter();


    
    /*------------------------------------------------------------------------*/
    /* Handle the main text file -- we want to open a new one if we are
       doing per chapter output. */
    
    String mainTextDisposition = Config.instance().getOutputScriptureTextDisposition();
    int chapterNo = Bookmark.instance().getChapter();
    BookElement be = m_BookElementsMap.get(Bookmark.instance().getUbsAbbreviatedName());
    if ("PerChapter".equalsIgnoreCase(mainTextDisposition))
    {
      String filePath = be.getOutputPathName(chapterNo);
      OutputManager.instance().setDestination(OutputManager.C_Destination_Scripture,
                                              filePath,
                                              Config.instance().getEncoding(),
                                              m_HeaderAndTrailerWriterScripture);
    }


    
    /*------------------------------------------------------------------------*/
    /* Handle footnotes.  Options for are as follows :-
       
       * None: Nothing to do.
       
       * Combined: Already taken care of in startRun -- nothing to do.
       
       * InSituInTextFile: Needs to point to the text file.  If we're doing
         a single combined text file, that will already have been set up
         anyway, but pointing to it again will do no harm.  If output is
         per book, then pointing to it is precisely what we want to do.
         If output is per chapter, then there's no harm in pointing -- it'll
         point at the wrong place, but we'll sort that out at the start of the
         chapter.
       
       * PerTextFile: Create a file if text output is per book.
       
       * AtEndOfTextFile: Create a temporary file if output is per book.
     */
    
    String disposition = Config.instance().getOutputFootnotesDisposition();
    if ("None".equalsIgnoreCase(disposition))
      ;
    else if ("Combined".equalsIgnoreCase(disposition))
      ;
    else if ("InSituInTextFile".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_Footnotes, OutputManager.C_Destination_Scripture);
    else if ("PerTextFile".equalsIgnoreCase(disposition) || "AtEndOfTextFile".equalsIgnoreCase(disposition))
    {
      if ("PerChapter".equalsIgnoreCase(mainTextDisposition))
      {
        String filePath = be.getFootnotesPathName(chapterNo);
        OutputManager.instance().setDestination(OutputManager.C_Destination_Footnotes,
                                                filePath,
                                                Config.instance().getEncoding(),
                                                m_HeaderAndTrailerWriterNotes);
      }
    }

    
    
    /*------------------------------------------------------------------------*/
    /* Handle endnotes.  Options for are as follows :-
       
       * None: Nothing to do.
       
       * Combined: Already taken care of in startRun -- nothing to do.
       
       * InSituInTextFile: Needs to point to the text file.  If we're doing
         a single combined text file, that will already have been set up
         anyway, but pointing to it again will do no harm.  If output is
         per book, then pointing to it is precisely what we want to do.
         If output is per chapter, then there's no harm in pointing -- it'll
         point at the wrong place, but we'll sort that out at the start of the
         chapter.
       
       * PerTextFile: Create a file if text output is per book.
       
       * AtEndOfTextFile: Create a temporary file if output is per book.
     */
    
    disposition = Config.instance().getOutputEndnotesDisposition();
    if ("None".equalsIgnoreCase(disposition))
      ;
    else if ("Combined".equalsIgnoreCase(disposition))
      ;
    else if ("InSituInTextFile".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_Endnotes, OutputManager.C_Destination_Scripture);
    else if ("WithFootnotes".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_Endnotes, OutputManager.C_Destination_Footnotes);
    else if ("PerTextFile".equalsIgnoreCase(disposition) || "AtEndOfTextFile".equalsIgnoreCase(disposition))
    {
      if ("PerChapter".equalsIgnoreCase(mainTextDisposition))
      {
        String filePath = be.getFootnotesPathName(chapterNo);
        OutputManager.instance().setDestination(OutputManager.C_Destination_Endnotes,
                                                filePath,
                                                Config.instance().getEncoding(),
                                                m_HeaderAndTrailerWriterNotes);
      }
    }

    
    
    /*------------------------------------------------------------------------*/
    /* Handle cross-references.  These are basically the same as footnotes,
       except that we also need to cater for the "With*Notes" options.  We can
       handle this by pointing to the notes destination; the only time when it
       would be vaguely wrong to do this would be when the notes is itself
       wrong (pending us hitting the start of a chapter), but then we can
       sort the cross-references out too when we hit start of chapter. */
       
    disposition = Config.instance().getOutputCrossReferencesDisposition();
    if ("None".equalsIgnoreCase(disposition))
      ;
    else if ("Combined".equalsIgnoreCase(disposition))
      ;
    else if ("InSituInTextFile".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_CrossReferences, OutputManager.C_Destination_Scripture);
    else if ("WithFootnotes".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_CrossReferences, OutputManager.C_Destination_Footnotes);
    else if ("WithEndnotes".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_CrossReferences, OutputManager.C_Destination_Endnotes);
    else if ("PerTextFile".equalsIgnoreCase(disposition) || "AtEndOfTextFile".equalsIgnoreCase(disposition))
    {
      if ("PerChapter".equalsIgnoreCase(mainTextDisposition))
      {
        String filePath = be.getCrossReferencesPathName(chapterNo);
        OutputManager.instance().setDestination(OutputManager.C_Destination_CrossReferences,
                                                filePath,
                                                Config.instance().getEncoding(),
                                                m_HeaderAndTrailerWriterCrossReferences);
      }
    }
 
    
    
    /*------------------------------------------------------------------------*/
    /* Handle introductory material -- as per cross-refernces. */
       
    disposition = Config.instance().getOutputIntroductionDisposition();
    if ("None".equalsIgnoreCase(disposition))
      ;
    else if ("Combined".equalsIgnoreCase(disposition))
      ;
    else if ("InSituInTextFile".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_Introduction, OutputManager.C_Destination_Scripture);
    else if ("WithFootnotes".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_Introduction, OutputManager.C_Destination_Footnotes);
    else if ("WithEndnotes".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_Introduction, OutputManager.C_Destination_Endnotes);
    else if ("PerTextFile".equalsIgnoreCase(disposition) || "AtEndOfTextFile".equalsIgnoreCase(disposition))
    {
      if ("PerChapter".equalsIgnoreCase(mainTextDisposition))
      {
        String filePath = be.getIntroPathName(chapterNo);
        OutputManager.instance().setDestination(OutputManager.C_Destination_Introduction,
                                                filePath,
                                                Config.instance().getEncoding(),
                                                m_HeaderAndTrailerWriterIntroduction);
      }
    }
  }

  
  /****************************************************************************/
  /**
   * Arranges for miscellaneous initialisation to occur (stuff which can't
   * run earlier), and also creates any files which are needed for the entire
   * duration of the run.
   * 
   * @throws IOException
   */
  
  public void startRun () throws IOException
  {
    /*------------------------------------------------------------------------*/
    generateListOfAllChaptersAvailableForCrossReferences();
    
    
    
    /*------------------------------------------------------------------------*/
    /* Handle the main text file -- we want to open a new one if we are
       doing combined output. */
    
    String mainTextDisposition = Config.instance().getOutputScriptureTextDisposition();
    if ("Combined".equalsIgnoreCase(mainTextDisposition))
    {
      String filePath = Config.instance().getOutputFolderPath() + Config.instance().getOutputScriptureTextFileName();
      OutputManager.instance().setDestination(OutputManager.C_Destination_Scripture,
                                              filePath,
                                              Config.instance().getEncoding(),
                                              m_HeaderAndTrailerWriterScripture);
    }


    
    /*------------------------------------------------------------------------*/
    /* Handle footnotes.  Options for are as follows :-
       
       * None: Nothing to do.
       
       * Combined: Create a file.
       
       * InSituInTextFile: Needs to point to the text file.
       
       * PerTextFile: Create a file if text output is combined.
       
       * AtEndOfTextFile: Create a temporary file if output is combined.
     */
    
    String disposition = Config.instance().getOutputFootnotesDisposition();
    if ("None".equalsIgnoreCase(disposition))
      ;
    else if ("Combined".equalsIgnoreCase(disposition))
    {
      String filePath = Config.instance().getOutputFolderPath() + Config.instance().getOutputFootnotesFileName();
      OutputManager.instance().setDestination(OutputManager.C_Destination_Footnotes,
                                              filePath,
                                              Config.instance().getEncoding(),
                                              m_HeaderAndTrailerWriterNotes);
    }
    else if ("InSituInTextFile".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_Footnotes, OutputManager.C_Destination_Scripture);
    else if ("PerTextFile".equalsIgnoreCase(disposition) || "AtEndOfTextFile".equalsIgnoreCase(disposition))
    {
      if ("Combined".equalsIgnoreCase(mainTextDisposition))
      {
        String filePath = Config.instance().getOutputFootnotesFileName();
        OutputManager.instance().setDestination(OutputManager.C_Destination_Footnotes,
                                                filePath,
                                                Config.instance().getEncoding(),
                                                m_HeaderAndTrailerWriterNotes);
      }
    }

    
    
    /*------------------------------------------------------------------------*/
    /* Handle cross-references.  These are basically the same as footnotes,
       except that we also need to cater for the "With*Notes" option.  We can
       handle this by pointing to the notes destination; the only time when it
       would be vaguely wrong to do this would be when the notes is itself
       wrong (pending us doing something else), but then we can sort the cross-
       references out too when we hit that something else. */
       
    disposition = Config.instance().getOutputCrossReferencesDisposition();
    if ("None".equalsIgnoreCase(disposition))
      ;
    else if ("Combined".equalsIgnoreCase(disposition))
    {
      String filePath = Config.instance().getOutputFolderPath() + Config.instance().getOutputCrossReferencesFileName();
      OutputManager.instance().setDestination(OutputManager.C_Destination_CrossReferences,
                                              filePath,
                                              Config.instance().getEncoding(),
                                              m_HeaderAndTrailerWriterCrossReferences);
    }
    else if ("InSituInTextFile".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_CrossReferences, OutputManager.C_Destination_Scripture);
    else if ("WithFootnotes".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_CrossReferences, OutputManager.C_Destination_Endnotes);
    else if ("WithEndnotes".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_CrossReferences, OutputManager.C_Destination_Footnotes);
    else if ("PerTextFile".equalsIgnoreCase(disposition) || "AtEndOfTextFile".equalsIgnoreCase(disposition))
    {
      if ("Combined".equalsIgnoreCase(mainTextDisposition))
      {
        String filePath = Config.instance().getOutputCrossReferencesFileName();
        OutputManager.instance().setDestination(OutputManager.C_Destination_CrossReferences,
                                                filePath,
                                                Config.instance().getEncoding(),
                                                m_HeaderAndTrailerWriterCrossReferences);
      }
    }
 
    
    
    /*------------------------------------------------------------------------*/
    /* Handle introductory material -- as per cross-refernces. */
       
    disposition = Config.instance().getOutputIntroductionDisposition();
    if ("None".equalsIgnoreCase(disposition))
      ;
    else if ("Combined".equalsIgnoreCase(disposition))
    {
      String filePath = Config.instance().getOutputFolderPath() + Config.instance().getOutputIntroductionFileName();
      OutputManager.instance().setDestination(OutputManager.C_Destination_Introduction,
                                              filePath,
                                              Config.instance().getEncoding(),
                                              m_HeaderAndTrailerWriterIntroduction);
    }
    else if ("InSituInTextFile".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_Introduction, OutputManager.C_Destination_Scripture);
    else if ("WithFootnotes".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_Introduction, OutputManager.C_Destination_Footnotes);
    else if ("WithEndnotes".equalsIgnoreCase(disposition))
      OutputManager.instance().linkDestination(OutputManager.C_Destination_Introduction, OutputManager.C_Destination_Endnotes);
    else if ("PerTextFile".equalsIgnoreCase(disposition) || "AtEndOfTextFile".equalsIgnoreCase(disposition))
    {
      if ("Combined".equalsIgnoreCase(mainTextDisposition))
      {
        String filePath = Config.instance().getOutputIntroductionFileName();
        OutputManager.instance().setDestination(OutputManager.C_Destination_Introduction,
                                                filePath,
                                                Config.instance().getEncoding(),
                                                m_HeaderAndTrailerWriterIntroduction);
      }
    }
  }


  



  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                                Private                                 **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /**
   * Constructor.
   */
  
  private BookAssembly ()
  {
  }
  
  
  /****************************************************************************/
  /**
   * Copies all selected files to the appropriate location.
   *
   * @param outputFolderPath
   *
   * @param filePaths File paths or file names.
   *
   * @param inputFolderPath Optional: prefixed to each input file name.
   *
   * @throws IOException
   */

  private void copyFiles (String outputFolderPath, 
                          ArrayList<String> filePaths,
                          String inputFolderPath) throws IOException
  {
    /**************************************************************************/
    if (0 == filePaths.size()) return;



    /**************************************************************************/
    /* If we need a subfolder to hold the data, make sure it exists. */

    File folder = new File(outputFolderPath);
    if (!folder.exists()) folder.mkdirs();



    /**************************************************************************/
    /* Copy the files. */

    for (String filePath : filePaths)
    {
      String fileName = (new File(filePath)).getName();
      Utils.fileCopy(outputFolderPath + fileName, inputFolderPath + filePath);
    }
  }


  /****************************************************************************/
  /* Copies the content of the footnote / cross-reference file to the end of
     the main text file, closes the former, and then deletes it.  Note that
     the main text file must still be open here. */
  
  private void copyFootnotesOrXRefToMainText (String notesFileName, String encoding) throws IOException
  {
    /*--------------------------------------------------*/
    if (!(new File(notesFileName).exists())) return;
    
    
    
    /*--------------------------------------------------*/
    FileInputStream fis = new FileInputStream(notesFileName);
    InputStreamReader isRdr = new InputStreamReader(fis, encoding);
    BufferedReader rdr = new BufferedReader(isRdr);



    /*--------------------------------------------------*/
    String s = null;
    if (null != m_StartOfTransferrableTextMarker && 0 != m_StartOfTransferrableTextMarker.length())
      while ((s = rdr.readLine()) != null)
        if (-1 != s.indexOf(m_StartOfTransferrableTextMarker))
	{
          OutputManager.instance().newLine(OutputManager.C_Destination_Scripture);
 	  OutputManager.instance().write(OutputManager.C_Destination_Scripture, s);
          OutputManager.instance().newLine(OutputManager.C_Destination_Scripture);
	  break;
        }



    /*--------------------------------------------------*/
    if (null != m_PrefixText)
      for (int i = 0; i < m_PrefixText.length; ++i)
      {
        OutputManager.instance().write(OutputManager.C_Destination_Scripture, m_PrefixText[i]);
        OutputManager.instance().newLine(OutputManager.C_Destination_Scripture);
      }
          


    /*--------------------------------------------------*/
    boolean lookForMarker = (null != m_EndOfTransferrableTextMarker && 
			     0 != m_EndOfTransferrableTextMarker.length());
    while ((s = rdr.readLine()) != null)
    {
      if (lookForMarker && (-1 != s.indexOf(m_EndOfTransferrableTextMarker)))
      {
        OutputManager.instance().write(OutputManager.C_Destination_Scripture, s);
        OutputManager.instance().newLine(OutputManager.C_Destination_Scripture);
        break;
      }
      
      OutputManager.instance().write(OutputManager.C_Destination_Scripture, s);
      OutputManager.instance().newLine(OutputManager.C_Destination_Scripture);
    }



   /*--------------------------------------------------*/
   if (null != m_SuffixText)
      for (int i = 0 ; i < m_SuffixText.length; ++i)
        OutputManager.instance().write(OutputManager.C_Destination_Scripture, m_SuffixText[i]);

   

    /*--------------------------------------------------*/
    fis.close();
    Utils.deleteFile(notesFileName);
  }


  /****************************************************************************/
  /** Copies any peripheral and image files we may have been called on to
      handle. */

  private void copyImageAndPeripheralFiles () throws IOException
  {
    BookAssemblyNonScriptureFilesIterator it = new BookAssemblyNonScriptureFilesIterator();
    ArrayList<String> list = new ArrayList<String>();

    while (it.hasNext())
    {
      String fileName = it.next().getInputFileName();
      if (0 != fileName.length()) list.add(fileName);
    }

    if (0 != list.size())
    {
      String inputFolder = Config.instance().getInputPeripheralMaterialFolderPath();
      copyFiles(Config.instance().getOutputFolderPath(), list, inputFolder);

      list.clear();
      String s = Utils.getFileNamesInFolder(Config.instance().getInputImageFolderPath(), "¬", true);
      if (0 != s.length())
      {
        String files[] = s.split("\\¬");
        for (int i = 0; i < files.length; ++i) list.add(files[i]);
        copyFiles(Config.instance().getOutputFolderPath() + "/images/", list, "");
      }
    }
  }


  /****************************************************************************/
  /**
     Call this at the end of processing for a given book.  It closes any files
     which were opened purely for this book or chapter.

     @throws IOException Any kind of problem at all.
  */

  private void endBook () throws IOException
  {
    endThing("PerChapter");
    endThing("PerBook");
  }

  
  /****************************************************************************/
  /**
     Call this at the end of processing for a given chapter.  It closes any
     files which were opened purely for this book or chapter.

     @throws Exception Any kind of problem at all.
  */

  private void endChapter () throws IOException
  {
    endThing("PerChapter");
  }

  
  /****************************************************************************/
  /* Carries out end-of-something processing. */

  private void endThing (String circs) throws IOException
  {
    /**************************************************************************/
    boolean closingCrossReferences  = false;
    boolean closingEndnotes         = false;
    boolean closingFootnotes        = false;
    boolean closingIntroduction     = false;
    boolean closingScripture        = false;

    String dispositionCrossReferences  = Config.instance().getOutputCrossReferencesDisposition();
    String dispositionEndnotes         = Config.instance().getOutputEndnotesDisposition();
    String dispositionFootnotes        = Config.instance().getOutputFootnotesDisposition();
    String dispositionIntroduction     = Config.instance().getOutputIntroductionDisposition();
    String dispositionScripture        = Config.instance().getOutputScriptureTextDisposition();

    String encodingCrossReferences  = OutputManager.instance().getEncoding(OutputManager.C_Destination_CrossReferences);
    String encodingEndnotes         = OutputManager.instance().getEncoding(OutputManager.C_Destination_Endnotes);
    String encodingFootnotes        = OutputManager.instance().getEncoding(OutputManager.C_Destination_Footnotes);
    String encodingIntroduction     = OutputManager.instance().getEncoding(OutputManager.C_Destination_Introduction);
    String encodingScripture        = OutputManager.instance().getEncoding(OutputManager.C_Destination_Scripture);

    String fileNameCrossReferences  = OutputManager.instance().getName(OutputManager.C_Destination_CrossReferences);
    String fileNameEndnotes         = OutputManager.instance().getName(OutputManager.C_Destination_Endnotes);
    String fileNameFootnotes        = OutputManager.instance().getName(OutputManager.C_Destination_Footnotes);
    String fileNameIntroduction     = OutputManager.instance().getName(OutputManager.C_Destination_Introduction);
    String fileNameScripture        = OutputManager.instance().getName(OutputManager.C_Destination_Scripture);

    boolean wasOpenCrossReferences  = OutputManager.instance().isOpen(OutputManager.C_Destination_CrossReferences);
    boolean wasOpenEndnotes         = OutputManager.instance().isOpen(OutputManager.C_Destination_Endnotes);
    boolean wasOpenFootnotes        = OutputManager.instance().isOpen(OutputManager.C_Destination_Footnotes);
    boolean wasOpenIntroduction     = OutputManager.instance().isOpen(OutputManager.C_Destination_Introduction);
    boolean wasOpenScripture        = OutputManager.instance().isOpen(OutputManager.C_Destination_Scripture);



    /**************************************************************************/
    /* Depending upon the configuration, we can in theory close scripture
       output on _any_ call -- per chapter, per book or combined.  The others
       can are sensitive here only to "combined", but it doesn't hurt to
       include them all in the same test. */

    closingScripture       = dispositionScripture.equalsIgnoreCase(circs);
    closingFootnotes       = dispositionFootnotes.equalsIgnoreCase(circs);
    closingEndnotes        = dispositionEndnotes.equalsIgnoreCase(circs);
    closingCrossReferences = dispositionCrossReferences.equalsIgnoreCase(circs);
    closingIntroduction    = dispositionIntroduction.equalsIgnoreCase(circs);



    /**************************************************************************/
    /* Where files are related back to the scripture file, we want to close
       them if we're closing the scripture file.  Note that not all
       options below are actually available to all of these outputs, but
       again for the sake of consistency there's no harm in including them
       all here. */

    if ("PerTextFile".equalsIgnoreCase(dispositionFootnotes) ||
	"AtEndOfTextFile".equalsIgnoreCase(dispositionFootnotes)||
	"InSituInTextFile".equalsIgnoreCase(dispositionFootnotes))
      closingFootnotes = closingFootnotes || closingScripture;

    if ("PerTextFile".equalsIgnoreCase(dispositionEndnotes) ||
	"AtEndOfTextFile".equalsIgnoreCase(dispositionEndnotes) ||
	"InSituInTextFile".equalsIgnoreCase(dispositionEndnotes))
      closingEndnotes = closingEndnotes || closingScripture;

    if ("PerTextFile".equalsIgnoreCase(dispositionCrossReferences) ||
	"AtEndOfTextFile".equalsIgnoreCase(dispositionCrossReferences) ||
	"InSituInTextFile".equalsIgnoreCase(dispositionCrossReferences))
      closingCrossReferences = closingCrossReferences || closingScripture;

    if ("PerTextFile".equalsIgnoreCase(dispositionIntroduction) ||
	"AtEndOfTextFile".equalsIgnoreCase(dispositionIntroduction) ||
	"InSituInTextFile".equalsIgnoreCase(dispositionIntroduction))
      closingIntroduction = closingIntroduction || closingIntroduction;



    /**************************************************************************/
    /* Where things are being sent to the same place as the footnotes,
       they need to close if it is closing. */

    if ("WithFootnotes".equalsIgnoreCase(dispositionEndnotes))
      closingEndnotes = closingEndnotes || closingFootnotes;

    if ("WithFootnotes".equalsIgnoreCase(dispositionCrossReferences))
      closingCrossReferences = closingCrossReferences || closingFootnotes;

    if ("WithFootnotes".equalsIgnoreCase(dispositionIntroduction))
      closingIntroduction = closingIntroduction || closingFootnotes;



    /**************************************************************************/
    /* Where things are being sent to the same place as the endnotes,
       they need to close if it is closing. */

    if ("WithEndnotes".equalsIgnoreCase(dispositionCrossReferences))
      closingCrossReferences = closingCrossReferences || closingEndnotes;

    if ("WithEndnotes".equalsIgnoreCase(dispositionIntroduction))
      closingIntroduction = closingIntroduction || closingEndnotes;



    /**************************************************************************/
    /* Time to do the actual closing.  We need to defer closing the main
       text file, however, until we've checked to see if we need to
       append anything to it. */

    if (closingIntroduction && wasOpenIntroduction)
      OutputManager.instance().close(OutputManager.C_Destination_Introduction);

    if (closingCrossReferences && wasOpenCrossReferences)
      OutputManager.instance().close(OutputManager.C_Destination_CrossReferences);

    if (closingEndnotes && wasOpenEndnotes)
      OutputManager.instance().close(OutputManager.C_Destination_Endnotes);

    if (closingFootnotes && wasOpenFootnotes)
      OutputManager.instance().close(OutputManager.C_Destination_Footnotes);




    /**************************************************************************/
    /* Where subsidiary files were to go at the end of the main scripture
       file, we need to move the details there.  This gets a bit complicated.
       Basically, we need to cater only for things which are to go at th
       end of the main file.  But some things my go there because they are
       flagged as going to the same place as something else, and this something
       else may be going to the end of the text file.  I _think_ it's ok simply
       to restrict ourselves to ones overtly flagged as AtEndOfTextFile, on the
       basis that where they cross-refer, we're bound to pick the right thing
       up sooner or later.  This finally leaves only the issue that we don't
       want to process the same file twice, but that's ok, because
       copyFootnotesOrXrefToMainTextFile deletes things after processing, and
       doesn't complain subsequently if it's called upon to process a file which
       doesn't exist.
     */
    
    if (closingIntroduction && closingScripture &&
        wasOpenIntroduction && wasOpenScripture &&
        dispositionIntroduction.equalsIgnoreCase("AtEndOfTextFile"))
      copyFootnotesOrXRefToMainText(fileNameIntroduction, encodingIntroduction);
    
    if (closingFootnotes && closingScripture &&
        wasOpenFootnotes && wasOpenScripture &&
        dispositionFootnotes.equalsIgnoreCase("AtEndOfTextFile"))
      copyFootnotesOrXRefToMainText(fileNameFootnotes, encodingFootnotes);
    
    if (closingCrossReferences && closingScripture &&
        wasOpenCrossReferences && wasOpenScripture &&
        dispositionCrossReferences.equalsIgnoreCase("AtEndOfTextFile"))
      copyFootnotesOrXRefToMainText(fileNameCrossReferences, encodingCrossReferences);
    
    if (closingEndnotes && closingScripture &&
        wasOpenEndnotes && wasOpenScripture &&
        dispositionEndnotes.equalsIgnoreCase("AtEndOfTextFile"))
      copyFootnotesOrXRefToMainText(fileNameEndnotes, encodingEndnotes);



    /**************************************************************************/
    /* Close the main scripture file if necessary. */

    if (closingScripture && wasOpenScripture)
      OutputManager.instance().close(OutputManager.C_Destination_Scripture);
  }


  /****************************************************************************/
  /* For cross-reference generation purposes, we need to know about all the
   * chapters in every book that we're processing, because it's safe to
   * generate a cross-reference which points to a chapter which exists, but
   * not to one which does not.  (We used to assume that if we merely had
   * a file for a given book, it was safe to generate refernces to that book,
   * but latterly we've been dealing with books for which we have only selected
   * chapters.)
   *
   * I'm assuming currently that we don't need to go down to verse level -- ie
   * I assume that if we have a given chapter, we will have all the verses for
   * it.
   */

  private void generateListOfAllChaptersAvailableForCrossReferences ()
    throws IOException
  {
    String inputFolderPath = Config.instance().getInputFolderPath();
    String[] files = Utils.getFileNamesInFolder(inputFolderPath, "!", false).split("!");
    for (String file: files)
    {
      HashMap<Integer, Object> chapterMap = new HashMap<Integer, Object>();
      String ubsAbbreviation = BibleDetails.instance().getAbbreviatedUbsNameFromFileName(file);
      generateListOfAllChaptersAvailableForCrossReferencesForBook(chapterMap, inputFolderPath, file);
      m_ListOfAllChaptersAvailableForCrossReferences.put(ubsAbbreviation, chapterMap);
    }
  }


  /****************************************************************************/
  /* Support routine for generateListOfAllChaptersAvailableForCrossReferences
   * (qv) which handles a single input file.
   */

  private void generateListOfAllChaptersAvailableForCrossReferencesForBook
   (HashMap<Integer, Object> chapterMap, String inputFolderPath, String fileName)
   throws IOException
  {
     FileInputStream fileInputStream = new FileInputStream(inputFolderPath + fileName);
     BufferedReader inputStreamReader = new BufferedReader(new InputStreamReader(fileInputStream, "UTF-8"));
     String s = "";
     Pattern p = Pattern.compile(".*\\<chapter[^>]+number\\s*\\=\\s*[\"'](\\d+)");

    while (null != (s = inputStreamReader.readLine()))
    {
      Matcher m = p.matcher(s);
      if (m.lookingAt()) chapterMap.put(Integer.parseInt(m.group(1)), null);
    }

     inputStreamReader.close();
     fileInputStream.close();
  }


  /****************************************************************************/
  /* Gets the list of files from the input folder. */

  private void readFileListFromInputFolder ()
  {
    if (m_BookElements.size() > 0) return;

    String inputFolderPath = Config.instance().getInputFolderPath();

    String[] files = Utils.getFileNamesInFolder(inputFolderPath, "!", false).split("!");

    for (int i = 0; i < files.length; ++i)
      m_BookElements.add((BookElement) new BookElementFromFileList(files[i]));
  }


  /****************************************************************************/
  private ArrayList<BookElement> m_BookElements = new ArrayList<BookElement>(120);
  private HashMap<String, BookElement> m_BookElementsMap = new HashMap<String, BookElement>(120);
  private int m_FirstAvailableChapterNumberForBook = 0;
  private static BookAssembly m_Instance = null;

  
  /****************************************************************************/
  /* See _setPrefixesAndSuffixes. */
  
  private String m_PrefixText[] = null;
  private String m_SuffixText[] = null;
  private String m_StartOfTransferrableTextMarker = null;
  private String m_EndOfTransferrableTextMarker = null;
  
  
  /****************************************************************************/
  /* Header and trailer writers. */
  
  private OutputStreamHeaderAndTrailerWriter m_HeaderAndTrailerWriterScripture = null;
  private OutputStreamHeaderAndTrailerWriter m_HeaderAndTrailerWriterNotes = null;
  private OutputStreamHeaderAndTrailerWriter m_HeaderAndTrailerWriterIntroduction = null;
  private OutputStreamHeaderAndTrailerWriter m_HeaderAndTrailerWriterCrossReferences = null;
  
  
    
  /****************************************************************************/
  /* A two level structure, the first keyed on UBS abbreviation, and the
   * second on chapter number.  We're interested only in whether the given
   * book/chapter combination exists.
   */
  
  private HashMap<String, HashMap<Integer, Object>> m_ListOfAllChaptersAvailableForCrossReferences =
    new HashMap<String, HashMap<Integer, Object>>();
}
